/*
 *    Copyright 2022 The DSMS Authors.
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *        http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */
package com.dsms.modules.system.service.impl;

import cn.hutool.extra.servlet.ServletUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.dsms.common.constant.ResultCode;
import com.dsms.common.model.LoginUser;
import com.dsms.common.model.Result;
import com.dsms.common.util.JwtUtil;
import com.dsms.modules.system.model.SmUser;
import com.dsms.modules.system.model.vo.LoginReqVO;
import com.dsms.modules.system.service.ISmUserService;
import com.dsms.modules.system.service.LoginService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Service;
import org.springframework.util.ObjectUtils;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.TimeUnit;


@Service
public class LoginServiceImpl implements LoginService {

    private static final int REDIS_TTL_MULTIPLE = 4;
    @Autowired
    RedisTemplate<String, Object> redisTemplate;
    @Autowired
    AuthenticationManager authenticationManager;
    @Autowired
    ISmUserService smUserService;
    @Autowired
    private JwtUtil jwtUtil;
    @Value("${jwt.config.ttl}")
    private long jwtTTl;

    @Override
    public Result<SmUser> login(HttpServletRequest request, HttpServletResponse response, LoginReqVO loginReqVO) {
        UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(loginReqVO.getUsername(), loginReqVO.getPassword());
        Authentication authenticate;
        try {
            authenticate = authenticationManager.authenticate(usernamePasswordAuthenticationToken);
        } catch (AuthenticationException e) {
            return Result.OK(ResultCode.LOGIN_FAIL);
        }

        //verify username and password
        if (ObjectUtils.isEmpty(authenticate)) {
            return Result.OK(ResultCode.LOGIN_FAIL);
        }
        LoginUser loginUser = (LoginUser) (authenticate.getPrincipal());
        Integer userId = loginUser.getUserId();

        //The map stores user information and login tokens
        Map<String, Object> map = new HashMap<>(8);
        String token = jwtUtil.createJWT(userId.toString(), loginUser.getUsername(), null);
        String refreshToken = UUID.randomUUID().toString();
        map.put("token", token);
        map.put("loginUser", loginUser);

        //store map in redis with key "refresh",expire time is
        redisTemplate.opsForHash().putAll(refreshToken, map);
        redisTemplate.expire(refreshToken, jwtTTl * REDIS_TTL_MULTIPLE, TimeUnit.MINUTES);

        //set token and refreshToken in response header
        response.setHeader("authorization", token);
        response.setHeader("refreshToken", refreshToken);
        response.setHeader("Access-Control-Expose-Headers", "authorization,refreshToken");

        //update login user info
        LambdaQueryWrapper<SmUser> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(SmUser::getUsername, loginUser.getUsername());
        SmUser user = smUserService.getOne(wrapper);
        user.setLastLoginDate(LocalDateTime.now());
        user.setLastLoginIp(ServletUtil.getClientIP(request));
        smUserService.updateById(user);

        return Result.OK(user);
    }

    @Override
    public Result<String> logout(HttpServletRequest request) {
        String refreshToken = request.getHeader("refreshToken");
        redisTemplate.delete(refreshToken);

        return Result.OK();
    }

    @Override
    public Result<SmUser> getLoginUser() {
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        LoginUser loginUser = (LoginUser) authentication.getPrincipal();
        SmUser userVO = smUserService.getById(loginUser.getUserId());

        return Result.OK(userVO);
    }
}
